﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Threading;
using System.Collections.ObjectModel;

namespace WebSocket
{
    public class WebSocketServer
    {
        private IPAddress address;

        private int port;

        private Socket server;

        public Method Method { get; set; }

        public bool IsRunning { get; set; }

        private ICollection<Socket> socketsPool;

        public event MessageEventHandler OnMessage;

        public event ErrorEventHandler OnError;

        public event LogInEventHandler OnLogin;

        public event LogOutEventHandler OnLogout;

        public event AcceptedEventHandler OnAccepted;

        public event RejectedEventHandler OnRejected;

        public event CloseEventHandler OnClose;

        public event OpenEventHandler OnOpen;

        public event HandshakeHandler OnHandShake;

        public WebSocketServer(IPAddress address, int port)
        {
            this.address = address;
            this.port = port;

            IPEndPoint ipe = new IPEndPoint(this.address, this.port);
            this.server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.server.Bind(ipe);

            // init the sockets pool
            socketsPool = new Collection<Socket>();

            // running the status
            this.IsRunning = true;
        }

        public void Listen()
        {
            this.Listen(int.MaxValue);
        }

        public void Listen(int backlog)
        {
            this.server.Listen(backlog);

            if (this.OnOpen != null)
            {
                this.OnOpen();
            }

            while (this.IsRunning)
            {
                Socket innerSocket = this.server.Accept();
                if (this.IsSocketAcceptCompleted(innerSocket))
                {
                    //
                    // accepted
                    if (this.OnAccepted != null)
                    {
                        this.OnAccepted(innerSocket);
                    }
                    // create a new thread to receive the data from socket
                    // once each socket connected the server, the thread will be started
                    // and if the client socket is shut down, the thread will abort.
                    new Thread(
                        delegate()
                        {
                            while (this.IsRunning)
                            {
                                bool shouldRemove = false;
                                try
                                {
                                    byte[] totalData = new byte[1024];
                                    int totalDataLength = innerSocket.Receive(totalData);
                                    
                                    // socket leave
                                    if (totalDataLength != 0)
                                    {
                                        byte[] realData = totalData.Where(d => d != 0x00 && d != 0xFF).ToArray();
                                        string dataStr = Encoding.UTF8.GetString(realData);
                                        if (this.OnMessage != null)
                                        {
                                            this.OnMessage(this, realData);
                                        }
                                    }
                                    else
                                    {
                                        shouldRemove = true;
                                    }
                                }
                                catch
                                {
                                    shouldRemove = true;
                                }
                                if (shouldRemove)
                                {
                                    this.socketsPool.Remove(innerSocket);
                                    Console.WriteLine(this.socketsPool.Count);
                                    if (this.OnLogout != null)
                                    {
                                        this.OnLogout(innerSocket);
                                    }
                                    break;
                                }
                            }
                        }).Start();
                }
                else
                {
                    // rejected
                    if (this.OnRejected != null)
                    {
                        this.OnRejected(innerSocket);
                    }
                }
            }
        }

        private bool IsSocketAcceptCompleted(Socket innerSocket)
        {
            bool result = true;

            byte[] receivedData = new byte[256];
            int dataLength = innerSocket.Receive(receivedData);

            Array.Resize(ref receivedData, dataLength);
            HandShake handShake = new HandShake(receivedData);

            byte[] computedBytes = handShake.GetComputedBytes();

            if (this.OnHandShake != null)
            {
                result = this.OnHandShake(handShake);
            }

            if (result)
            {
                innerSocket.Send(computedBytes);

                socketsPool.Add(innerSocket);
                Console.WriteLine(socketsPool.Count);
                if (this.OnLogin != null)
                {
                    this.OnLogin(innerSocket);
                }
            }
            return result;
        }

        public void Broadcast(byte[] sendData)
        {
            byte[] sendFrame = new byte[sendData.Length + 2];

            sendFrame[0] = 0x00;
            sendFrame[sendData.Length + 1] = 0xFF;

            Array.Copy(sendData, 0, sendFrame, 1, sendData.Length);
            // Boradcast one message.
            foreach (Socket s in this.socketsPool)
            {
                s.Send(sendFrame);
            }
        }

        public void Close()
        {
            this.IsRunning = false;
            foreach (Socket s in this.socketsPool)
            {
                s.Close();
            }
            this.OnClose();
        }
    }
}
